package com.hearsilent.discreteslider;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

import java.util.ArrayList;
import java.util.List;

import com.hearsilent.discreteslider.libs.Utils;
import com.hearsilent.discreteslider.libs.TextUtils;
import com.hearsilent.discreteslider.libs.MoveGestureDetector;
import com.hearsilent.discreteslider.libs.Dash;

import com.hearsilent.discreteslider.libs.AttrUtils;
import ohos.agp.animation.Animator;
import ohos.agp.animation.AnimatorValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.ComponentParent;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;

import ohos.agp.utils.RectFloat;
import ohos.agp.utils.Rect;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Point;
import ohos.agp.utils.TextAlignment;

import ohos.app.Context;
import ohos.eventhandler.EventHandler;
import ohos.eventhandler.EventRunner;
import ohos.multimodalinput.event.TouchEvent;

/**
 * DiscreteSlider
 */
public class DiscreteSlider extends Component implements Component.DrawTask {
    private static final int HORIZONTAL = 0;
    private static final int VERTICAL = 1;

    private static final int TOP = 0;
    private static final int RIGHT = 90;
    private static final int BOTTOM = 180;
    private static final int LEFT = 270;

    private static final int MODE_NORMAL = 0;
    private static final int MODE_RANGE = 1;

    private Paint mPaint = new Paint();
    private RectFloat mRectF = new RectFloat();

    private float mOffset = 0f;
    private float mMinOffset;
    private float mMaxOffset;

    private float mRadius;
    private float mTrackWidth;

    private int mTrackColor;
    private int mInactiveTrackColor;
    private int mThumbColor;
    private int mThumbPressedColor;

    private List<Object> mTickMarkPatterns;
    private int mTickMarkColor;
    private int mTickMarkInactiveColor;
    private int mTickMarkStep;

    private int mValueLabelTextColor;

    private int mCount;
    private int mProgressOffset = 0;
    private int mMinProgress = 0;
    private int mTmpMinProgress = 0;
    private int mMaxProgress = -1;
    private int mTmpMaxProgress = -1;
    private int mPendingPosition = -1;
    private int mPressedPosition = -1;

    @Mode
    private int mMode = MODE_NORMAL;

    private Path mInactiveTrackPath = new Path();

    private MoveGestureDetector mMoveDetector;
    private float mValueLabelTextSize;
    private ValueLabelFormatter mValueLabelFormatter;
    private Rect mBounds = new Rect();
    private Path mValueLabelPath = new Path();
    private AnimatorValue mValueLabelAnimator;
    private Matrix mValueLabelMatrix = new Matrix();
    private float mValueLabelAnimValue = 0f;
    @ValueLabelGravity
    private int mValueLabelGravity;
    private int mValueLabelMode = 1;
    private int mValueLabelDuration = 1500;
    private MyEventHandler mShowValueLabelHandler;
    private boolean mValueLabelIsShowing = false;

    private boolean mSkipMove;

    private float mLength;
    @OrientationMode
    private int mOrientation;

    private OnValueChangedListener mListener;
    private boolean mValueChangedImmediately = false;

    class MyEventHandler extends EventHandler {
        MyEventHandler(EventRunner runner) throws IllegalArgumentException {
            super(runner);
        }
    }

    @Retention(RetentionPolicy.SOURCE)
    private @interface Mode {
    }

    @Retention(RetentionPolicy.SOURCE)
    private @interface ValueLabelGravity {
    }

    @Retention(RetentionPolicy.SOURCE)
    private @interface OrientationMode {
    }

    public DiscreteSlider(Context context) {
        this(context, null);
    }

    public DiscreteSlider(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public DiscreteSlider(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttrSet attrs) {
        mPaint.setStyle(Paint.Style.FILL_STYLE);
        mShowValueLabelHandler = new MyEventHandler(EventRunner.getMainEventRunner());

        if (attrs != null) {
            mTrackWidth = AttrUtils.getDimensionFromAttr(attrs, "ds_trackWidth",
                    Utils.convertDpToPixel(4, getContext()));
            mTrackWidth = Math.max(mTrackWidth, Float.MIN_VALUE);

            mTrackColor = AttrUtils.getColorFromAttr(attrs, "ds_trackColor", 0xff5123da);
            mInactiveTrackColor = AttrUtils.getColorFromAttr(attrs, "ds_inactiveTrackColor", 0x3d5123da);

            mRadius = AttrUtils.getDimensionFromAttr(attrs, "ds_thumbRadius",
                    Utils.convertDpToPixel(6, getContext()));
            mRadius = Math.max(mRadius, Float.MIN_VALUE);
            mThumbColor = AttrUtils.getColorFromAttr(attrs, "ds_thumbColor", 0xff5123da);

            mThumbPressedColor = AttrUtils.getColorFromAttr(attrs, "ds_thumbPressedColor", 0x1f5123da);
            mTickMarkColor = AttrUtils.getColorFromAttr(attrs, "ds_tickMarkColor", 0xff9972ed);
            mTickMarkInactiveColor = AttrUtils.getColorFromAttr(attrs, "ds_tickMarkInactiveColor", 0xff936ce2);

            mTickMarkStep = AttrUtils.getIntFromAttr(attrs, "ds_tickMarkStep", 1);

            mValueLabelTextColor = AttrUtils.getColorFromAttr(attrs, "ds_valueLabelTextColor", 0xffffffff);

            mValueLabelTextSize = AttrUtils.getDimensionFromAttr(attrs, "ds_valueLabelTextSize",
                    Utils.convertDpToPixel(16, getContext()));

            mValueLabelGravity = AttrUtils.getIntFromAttr(attrs, "ds_valueLabelGravity", TOP);
            mValueLabelMode = AttrUtils.getIntFromAttr(attrs, "ds_valueLabelMode", 1);
            mValueLabelDuration = AttrUtils.getIntFromAttr(attrs, "ds_valueLabelDuration", 1500);
            mValueLabelDuration = Math.max(mValueLabelDuration, 500);

            mCount = AttrUtils.getIntFromAttr(attrs, "ds_count", 11);
            mCount = Math.max(mCount, 2);
            mMode = AttrUtils.getIntFromAttr(attrs, "ds_mode", MODE_NORMAL);

            if (mTickMarkStep < 1 || (mCount - 1) % mTickMarkStep != 0) {
                mTickMarkStep = 1;
            }

            mProgressOffset = AttrUtils.getIntFromAttr(attrs, "ds_progressOffset", 0);
            mTmpMinProgress = AttrUtils.getIntFromAttr(attrs, "ds_progress",
                    AttrUtils.getIntFromAttr(attrs, "ds_minProgress", 0));

            if (mMode == MODE_NORMAL) {
                mTmpMaxProgress = mMaxProgress = -1;
            } else {
                mTmpMaxProgress = mMaxProgress =
                        AttrUtils.getIntFromAttr(attrs, "ds_maxProgress", mCount - 1);
            }

            if (AttrUtils.getBooleanFromAttr(attrs, "ds_tickMarkPatterns", false)) {
                String patterns = AttrUtils.getStringFromAttr(attrs, "ds_tickMarkPatterns", "");

                if (!TextUtils.isEmpty(patterns)) {
                    float length = AttrUtils.getDimensionFromAttr(attrs, "ds_tickMarkDashLength",
                            Utils.convertDpToPixel(1, getContext()));
                    mTickMarkPatterns = new ArrayList<>();
                    if (patterns.contains(",")) {
                        for (String pattern : patterns.split(",")) {
                            if (pattern.equalsIgnoreCase("dot")) {
                                mTickMarkPatterns.add(new Dot());
                            } else if (pattern.equalsIgnoreCase("dash")) {
                                mTickMarkPatterns.add(new Dash(length));
                            }
                        }
                    } else {
                        if (patterns.equalsIgnoreCase("dot")) {
                            mTickMarkPatterns.add(new Dot());
                        } else if (patterns.equalsIgnoreCase("dash")) {
                            mTickMarkPatterns.add(new Dash(length));
                        }
                    }
                }
            }
            mOrientation = AttrUtils.getIntFromAttr(attrs, "ds_orientation", HORIZONTAL);

            if (mOrientation == HORIZONTAL &&
                    (mValueLabelGravity != TOP && mValueLabelGravity != BOTTOM)) {
                mValueLabelGravity = TOP;
            } else if (mOrientation == VERTICAL &&
                    (mValueLabelGravity != RIGHT && mValueLabelGravity != LEFT)) {
                mValueLabelGravity = RIGHT;
            }
            setMode(mMode);
        } else {
            mTrackWidth = Utils.convertDpToPixel(4, context);
            mTrackColor = 0xff5123da;
            mInactiveTrackColor = 0x3d5123da;

            mRadius = Utils.convertDpToPixel(6, context);
            mThumbColor = 0xff5123da;
            mThumbPressedColor = 0x1f5123da;

            mTickMarkColor = 0xff9972ed;
            mTickMarkInactiveColor = 0xff936ce2;
            mTickMarkStep = 1;

            mValueLabelTextSize = Utils.convertDpToPixel(16, context);
            mValueLabelTextColor = Color.WHITE.getValue();
            mValueLabelGravity = TOP;
            mOrientation = HORIZONTAL;
            mCount = 11;
        }

        setValueLabelFormatter(new ValueLabelFormatter() {
            @Override
            public String getLabel(int input) {
                return Integer.toString(input);
            }
        });

        mMoveDetector = new MoveGestureDetector(context, new MoveListener());
        addDrawTask(this);
        setTouchEventListener(new TouchEventListener() {
            @Override
            public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
                handleTouchEvent(touchEvent);
                return true;
            }
        });
    }

    /**
     * 设置 TrackWidth
     *
     * @param trackWidth trackWidth
     */
    public void setTrackWidth(float trackWidth) {
        if (trackWidth <= 0) {
            throw new IllegalArgumentException("Track width must be a positive number.");
        }
        mTrackWidth = trackWidth;
        generateInactiveTrackPath();
        invalidate();
    }

    /**
     * 获取 TrackWidth
     *
     * @return mTrackWidth
     */
    public float getTrackWidth() {
        return mTrackWidth;
    }

    /**
     * 设置 TrackColor
     *
     * @param trackColor trackColor
     */
    public void setTrackColor(int trackColor) {
        mTrackColor = trackColor;
        invalidate();
    }

    /**
     * 获取 TrackColor
     *
     * @return mInactiveTrackColor
     */
    public int getTrackColor() {
        return mInactiveTrackColor;
    }

    /**
     * 设置 InactiveTrackColor
     *
     * @param inactiveTrackColor inactiveTrackColor
     */
    public void setInactiveTrackColor(int inactiveTrackColor) {
        mInactiveTrackColor = inactiveTrackColor;
        invalidate();
    }


    /**
     * 获取 InactiveTrackColor
     *
     * @return mInactiveTrackColor
     */
    public int getInactiveTrackColor() {
        return mInactiveTrackColor;
    }

    /**
     * 设置 ThumbRadius
     *
     * @param radius radius
     */
    public void setThumbRadius(float radius) {
        if (radius <= 0) {
            throw new IllegalArgumentException("Thumb radius must be a positive number.");
        }
        mRadius = radius;
        generateInactiveTrackPath();
        invalidate();
    }

    /**
     * 获取 ThumbRadius
     *
     * @return mRadius
     */
    public float getThumbRadius() {
        return mRadius;
    }

    /**
     * 设置 ThumbColor
     *
     * @param thumbColor thumbColor
     */
    public void setThumbColor(int thumbColor) {
        mThumbColor = thumbColor;
        invalidate();
    }


    /**
     * 获取 ThumbColor
     *
     * @return mThumbColor
     */
    public int getThumbColor() {
        return mThumbColor;
    }

    /**
     * 设置 ThumbPressedColor
     *
     * @param thumbPressedColor thumbPressedColor
     */
    public void setThumbPressedColor(int thumbPressedColor) {
        mThumbPressedColor = thumbPressedColor;
        invalidate();
    }


    /**
     * 获取 ThumbPressedColor
     *
     * @return mThumbPressedColor
     */
    public int getThumbPressedColor() {
        return mThumbPressedColor;
    }

    /**
     * 设置 TickMarkColor
     *
     * @param tickMarkColor tickMarkColor
     */
    public void setTickMarkColor(int tickMarkColor) {
        mTickMarkColor = tickMarkColor;
        invalidate();
    }

    /**
     * 获取 TickMarkColor
     *
     * @return TickMarkColor
     */
    public int getTickMarkColor() {
        return mTickMarkColor;
    }

    /**
     * 设置 TickMarkInactiveColor
     *
     * @param tickMarkInactiveColor tickMarkInactiveColor
     */
    public void setTickMarkInactiveColor(int tickMarkInactiveColor) {
        mTickMarkInactiveColor = tickMarkInactiveColor;
        invalidate();
    }


    /**
     * 获取TickMarkInactiveColor
     *
     * @return mTickMarkInactiveColor
     */
    public int getTickMarkInactiveColor() {
        return mTickMarkInactiveColor;
    }

    /**
     * 获取 TickMarkStep
     *
     * @return mTickMarkStep
     */
    public int getTickMarkStep() {
        return mTickMarkStep;
    }

    /**
     * 设置TickMarkStep
     *
     * @param tickMarkStep tickMarkStep
     */
    public void setTickMarkStep(int tickMarkStep) {
        if (tickMarkStep < 1) {
            throw new IllegalArgumentException("TickMark step must >= 1.");
        }
        if ((mCount - 1) % tickMarkStep != 0) {
            throw new IllegalArgumentException(
                    "TickMark step must be a factor of " + (mCount - 1) + ".");
        }
        mTickMarkStep = tickMarkStep;
    }

    /**
     * 设置 ValueLabelTextColor
     *
     * @param valueLabelTextColor valueLabelTextColor
     */
    public void setValueLabelTextColor(int valueLabelTextColor) {
        mValueLabelTextColor = valueLabelTextColor;
        invalidate();
    }

    /**
     * 获取 ValueLabelTextColor
     *
     * @return ValueLabelTextColor
     */
    public int getValueLabelTextColor() {
        return mValueLabelTextColor;
    }

    /**
     * 设置 ValueLabelTextSize
     *
     * @param valueLabelTextSize valueLabelTextSize
     */
    public void setValueLabelTextSize(float valueLabelTextSize) {
        if (valueLabelTextSize <= 0) {
            throw new IllegalArgumentException("Value label text size must be a positive number.");
        }
        mValueLabelTextSize = valueLabelTextSize;
        invalidate();
    }

    /**
     * 获取 ValueLabelTextSize
     *
     * @return mValueLabelTextSize
     */
    public float getValueLabelTextSize() {
        return mValueLabelTextSize;
    }

    /**
     * 设置ValueLabelGravity
     *
     * @param valueLabelGravity valueLabelGravity
     */
    public void setValueLabelGravity(@ValueLabelGravity int valueLabelGravity) {
        if (mOrientation == HORIZONTAL && valueLabelGravity != TOP && valueLabelGravity != BOTTOM) {
            throw new IllegalArgumentException(
                    "Horizontal orientation value label gravity must be top or bottom.");
        } else if (mOrientation == VERTICAL && valueLabelGravity != RIGHT &&
                valueLabelGravity != LEFT) {
            throw new IllegalArgumentException(
                    "Vertical orientation value label gravity must be right or left.");
        }
        mValueLabelGravity = valueLabelGravity;
        invalidate();
    }

    @ValueLabelGravity
    /**
     * 获取ValueLabelGravity
     */
    public int getValueLabelGravity() {
        return mValueLabelGravity;
    }

    /**
     * 设置 Mode
     *
     * @param mode mode
     */
    public void setMode(int mode) {
        if (mode != MODE_RANGE && mode != MODE_NORMAL) {
            throw new IllegalArgumentException("Mode must be normal or range.");
        }
        mMode = mode;
        checkProgressBound();
        invalidate();
    }

    /**
     * 获取 Mode
     *
     * @return Mode
     */
    public int getMode() {
        return mMode;
    }

    /**
     * 设置 Count
     *
     * @param count count
     */
    public void setCount(int count) {
        if (count < 2) {
            throw new IllegalArgumentException("Count must larger than 2.");
        }
        mCount = count;
        checkProgressBound();
        invalidate();
    }

    /**
     * 获取 Count
     *
     * @return Count
     */
    public int getCount() {
        return mCount;
    }

    /**
     * 设置 TickMarkPatterns
     *
     * @param patterns patterns
     */
    public void setTickMarkPatterns(List<Object> patterns) {
        if (patterns == null) {
            mTickMarkPatterns = null;
        } else {
            for (Object pattern : patterns) {
                if (!(pattern instanceof Dot) && !(pattern instanceof Dash)) {
                    throw new IllegalArgumentException("Pattern only accepted dot or dash.");
                }
            }
            mTickMarkPatterns = patterns;
        }
        generateInactiveTrackPath();
        invalidate();
    }


    /**
     * 获取 TickMarkPatterns
     *
     * @return mTickMarkPatterns
     */
    public List<Object> getTickMarkPatterns() {
        return mTickMarkPatterns;
    }

    /**
     * 设置ValueLabelFormatter
     *
     * @param formatter formatter
     */
    public void setValueLabelFormatter(ValueLabelFormatter formatter) {
        mValueLabelFormatter = formatter;
    }

    /**
     * 获取 ValueLabelFormatter
     *
     * @return ValueLabelFormatter
     */
    public ValueLabelFormatter getValueLabelFormatter() {
        return mValueLabelFormatter;
    }

    /**
     * 设置 ValueLabelMode
     *
     * @param mode mode
     */
    public void setValueLabelMode(int mode) {
        mValueLabelMode = mode;
        invalidate();
    }

    /**
     * 获取 ValueLabelMode
     *
     * @return ValueLabelMode
     */
    public int getValueLabelMode() {
        return mValueLabelMode;
    }

    /**
     * 设置 ValueLabelDuration
     *
     * @param duration
     */
    public void setValueLabelDuration(int duration) {
        mValueLabelDuration = duration;
        invalidate();
    }

    /**
     * 获取 ValueLabelDuration
     *
     * @return ValueLabelDuration
     */
    public int getValueLabelDuration() {
        return mValueLabelDuration;
    }

    /**
     * 设置 ProgressOffset
     *
     * @param progressOffset progressOffset
     */
    public void setProgressOffset(int progressOffset) {
        mProgressOffset = progressOffset;
        invalidate();
    }

    /**
     * 设置值
     *
     * @param progress 值
     */
    public void setProgress(int progress) {
        setMinProgress(progress);
    }

    /**
     * 设置最小值
     *
     * @param progress 最小值
     */
    public void setMinProgress(int progress) {
        boolean isTouchOnMinProgress = mPendingPosition == mMinProgress;

        int _progress = mMinProgress;
        mMinProgress = progress;
        checkProgressBound();
        if (_progress != mMinProgress && mListener != null) {
            if (mMaxProgress != -1 && mMode != MODE_NORMAL) {
                mListener.onValueChanged(mMinProgress + mProgressOffset,
                        mMaxProgress + mProgressOffset, false);
            } else {
                mListener.onValueChanged(mMinProgress + mProgressOffset, false);
            }
        }

        if ((mValueLabelMode >> 1 & 0x1) == 1 && (mPendingPosition == -1 || isTouchOnMinProgress)) {
            showMinValueLabel();
        } else if (mPendingPosition != -1) {
            if (isTouchOnMinProgress) {
                mPendingPosition = mMinProgress;
                if (mPressedPosition != -1) {
                    mPressedPosition = mMinProgress;
                }
            } else {
                mPendingPosition = mMaxProgress;
                if (mPressedPosition != -1) {
                    mPressedPosition = mMaxProgress;
                }
            }
            generateValueLabelPath();
        }
        checkOffsetBounds(mPressedPosition != -1);
        invalidate();
    }

    /**
     * 获取Progress
     *
     * @return Progress
     */
    public int getProgress() {
        return getMinProgress();
    }

    /**
     * 获取最大值
     *
     * @return 最大值
     */
    public int getMinProgress() {
        return mMinProgress;
    }

    /**
     * setMaxProgress
     *
     * @param progress 最大值
     */
    public void setMaxProgress(int progress) {
        if (mMode != MODE_RANGE) {
            throw new IllegalStateException("Set max progress must be range mode.");
        }
        boolean isTouchOnMinProgress = mPendingPosition == mMinProgress;
        int _progress = mMaxProgress;
        mMaxProgress = progress;
        checkProgressBound();
        if (_progress != mMaxProgress && mListener != null) {
            if (mMaxProgress != -1 && mMode != MODE_NORMAL) {
                mListener.onValueChanged(mMinProgress + mProgressOffset,
                        mMaxProgress + mProgressOffset, false);
            }
        }

        if ((mValueLabelMode >> 1 & 0x1) == 1 && (mPendingPosition == -1 || !isTouchOnMinProgress)) {
            showMaxValueLabel();
        } else if (mPendingPosition != -1) {
            if (isTouchOnMinProgress) {
                mPendingPosition = mMinProgress;
                if (mPressedPosition != -1) {
                    mPressedPosition = mMinProgress;
                }
            } else {
                mPendingPosition = mMaxProgress;
                if (mPressedPosition != -1) {
                    mPressedPosition = mMaxProgress;
                }
            }
            generateValueLabelPath();
        }
        checkOffsetBounds(mPressedPosition != -1);

        invalidate();
    }

    /**
     * MaxProgress
     *
     * @return 最大值
     */
    public int getMaxProgress() {
        return mMaxProgress;
    }

    private void checkProgressBound() {
        if (mMode != MODE_NORMAL) {
            if (mMaxProgress == -1) {
                mMaxProgress = mCount - 1;
            } else if (mMaxProgress > mCount - 1) {
                mMaxProgress = mCount - 1;
            }
            if (mMinProgress >= mMaxProgress) {
                mMinProgress = mMaxProgress - 1;
            }
        } else {
            mMaxProgress = -1;
            if (mMinProgress > mCount - 1) {
                mMinProgress = mCount - 1;
            }
        }
        mTmpMinProgress = mMinProgress;
        mTmpMaxProgress = mMaxProgress;
    }

    /**
     * ValueChangedListener
     *
     * @param listener listener
     */
    public void setOnValueChangedListener(OnValueChangedListener listener) {
        mListener = listener;
    }

    /**
     * ValueChangedImmediately
     *
     * @param immediately immediately
     */
    public void setValueChangedImmediately(boolean immediately) {
        mValueChangedImmediately = immediately;
    }

    private void generateInactiveTrackPath() {
        float radius = mTrackWidth / 2f;
        float left;
        float top;
        float right;
        float bottom;
        mInactiveTrackPath.reset();
        if (mOrientation == HORIZONTAL) {
            mLength = getWidth() - getPaddingLeft() - getPaddingRight() - mRadius * 2 + mTrackWidth;
            left = getPaddingLeft() + mRadius - radius;
            top = ((getHeight() - getPaddingTop() - getPaddingBottom()) - mTrackWidth) / 2f +
                    getPaddingTop();
            right = left + mLength;
            bottom = top + mTrackWidth;
            if (mTickMarkPatterns != null && mTickMarkPatterns.size() > 0) {
                if (mTickMarkPatterns.get(0) instanceof Dot) {
                    mRectF.modify(left, top, left + mTrackWidth, bottom);

                    mInactiveTrackPath.arcTo(mRectF, 90, 180, true);
                } else {
                    mInactiveTrackPath.moveTo(left, bottom);
                    mInactiveTrackPath.lineTo(left, top);
                }
                if (mTickMarkPatterns.get((mCount - 1) % mTickMarkPatterns.size()) instanceof Dot) {
                    mInactiveTrackPath.lineTo(right - radius, top);
                    mRectF.modify(right - mTrackWidth, top, right, bottom);
                    mInactiveTrackPath.arcTo(mRectF, -90, 180, true);
                } else {
                    mInactiveTrackPath.lineTo(right, top);
                    mInactiveTrackPath.lineTo(right, bottom);
                }
                if (mTickMarkPatterns.get(0) instanceof Dot) {
                    mInactiveTrackPath.lineTo(left + radius, bottom);
                } else {
                    mInactiveTrackPath.lineTo(left, bottom);
                }
                mInactiveTrackPath.close();
            } else {
                mRectF.modify(left, top, right, bottom);
                mInactiveTrackPath.addRoundRect(mRectF, radius, radius, Path.Direction.COUNTER_CLOCK_WISE);
            }
        } else {
            mLength =
                    getHeight() - getPaddingTop() - getPaddingBottom() - mRadius * 2 + mTrackWidth;
            left = ((getWidth() - getPaddingLeft() - getPaddingRight()) - mTrackWidth) / 2f +
                    getPaddingLeft();
            top = getPaddingTop() + mRadius - radius;
            right = left + mTrackWidth;
            bottom = top + mLength;
            if (mTickMarkPatterns != null && mTickMarkPatterns.size() > 0) {
                if (mTickMarkPatterns.get(0) instanceof Dot) {
                    mRectF.modify(left, top, right, top + mTrackWidth);
                    mInactiveTrackPath.arcTo(mRectF, 180, 180, true);
                } else {
                    mInactiveTrackPath.moveTo(left, top);
                    mInactiveTrackPath.lineTo(right, top);
                }
                if (mTickMarkPatterns.get((mCount - 1) % mTickMarkPatterns.size()) instanceof Dot) {
                    mInactiveTrackPath.lineTo(right, bottom - radius);
                    mRectF.modify(left, bottom - mTrackWidth, right, bottom);
                    mInactiveTrackPath.arcTo(mRectF, 0, 180, true);
                } else {
                    mInactiveTrackPath.lineTo(right, bottom);
                    mInactiveTrackPath.lineTo(left, bottom);
                }
                if (mTickMarkPatterns.get(0) instanceof Dot) {
                    mInactiveTrackPath.lineTo(left, top + radius);
                } else {
                    mInactiveTrackPath.lineTo(left, top);
                }
                mInactiveTrackPath.close();
            } else {
                mRectF.modify(left, top, right, bottom);
                mInactiveTrackPath.addRoundRect(mRectF, radius, radius, Path.Direction.COUNTER_CLOCK_WISE);
            }
        }
    }


    /**
     * performClick
     *
     * @return false
     */
    public boolean performClick() {
        return false;
    }


    private boolean onTouchEvent(TouchEvent event) {
        performClick();
        return isEnabled() && handleTouchEvent(event);
    }

    private void requestDisallowInterceptTouchEvent(ComponentParent parent, boolean isDragging) {
        if (parent != null) {
            requestDisallowInterceptTouchEvent(parent.getComponentParent(), isDragging);
        }
    }

    private boolean handleTouchEvent(TouchEvent event) {
        if (mCount < 2) {
            mMoveDetector.onTouchEvent(event);
            return true;
        }
        float length = mLength - mTrackWidth;
        float ps = mOrientation == HORIZONTAL ? event.getPointerPosition(0)
                .getX() : event.getPointerPosition(0).getY();

        if (event.getAction() == TouchEvent.PRIMARY_POINT_DOWN) {
            down(ps, length);
        } else if (event.getAction() == TouchEvent.POINT_MOVE) {
            int pendingPosition = mPendingPosition;
            mOffset = 0;
            mPendingPosition = -1;
            mSkipMove = false;

            if (mMaxProgress == -1 && mMode == MODE_NORMAL) {
                float cs = getPosition(length, mMinProgress, false);
                if (cs - mRadius * 3.5 <= ps && ps <= cs + mRadius * 3.5) {
                    mPendingPosition = mMinProgress;
                }
            } else {
                float c1 = getPosition(length, mMinProgress, false);
                float c2 = getPosition(length, mMaxProgress, false);
                if (c1 - mRadius * 3.5 <= ps && ps <= c1 + mRadius * 3.5) {
                    mPendingPosition = mMinProgress;
                } else if (c2 - mRadius * 3.5 <= ps && ps <= c2 + mRadius * 3.5) {
                    mPendingPosition = mMaxProgress;
                }
            }
            if (mPendingPosition == -1) {
                mPendingPosition = (int) getClosestPosition(ps, length)[0];
            }
            if (true) {
                if (mPendingPosition != mMinProgress &&
                        !(mPendingPosition == mMaxProgress && mMode != MODE_NORMAL)) {
                    if (mValueLabelAnimator == null) {
                        animValueLabel();
                    }
                }
                generateValueLabelPath();
                if (mMaxProgress != -1 && mMode == MODE_RANGE) {
                    if (Math.abs(mMinProgress - mPendingPosition) >
                            Math.abs(mMaxProgress - mPendingPosition)) {
                        mMaxProgress = mPendingPosition;
                    } else {
                        mMinProgress = mPendingPosition;
                    }
                } else {
                    mMinProgress = mPendingPosition;
                }
            }

            checkOffsetBounds(true);
            invalidate();
        } else if (event.getAction() == TouchEvent.PRIMARY_POINT_UP) {
            cancell();
        }
        return true;
    }

    private void down(float ps, float length) {
        int pendingPosition = mPendingPosition;
        mOffset = 0;
        mPendingPosition = -1;
        mSkipMove = false;

        if (mMaxProgress == -1 && mMode == MODE_NORMAL) {
            float cs = getPosition(length, mMinProgress, false);
            if (cs - mRadius * 3.5 <= ps && ps <= cs + mRadius * 3.5) {
                mPendingPosition = mMinProgress;
            }
        } else {
            float c1 = getPosition(length, mMinProgress, false);
            float c2 = getPosition(length, mMaxProgress, false);
            if (c1 - mRadius * 3.5 <= ps && ps <= c1 + mRadius * 3.5) {
                mPendingPosition = mMinProgress;
            } else if (c2 - mRadius * 3.5 <= ps && ps <= c2 + mRadius * 3.5) {
                mPendingPosition = mMaxProgress;
            }
        }
        if (mPendingPosition == -1) {
            mPendingPosition = (int) getClosestPosition(ps, length)[0];
        }

        if (pendingPosition != mPendingPosition) {
            if (mValueLabelAnimator != null) {
                mValueLabelAnimator.cancel();
                mValueLabelAnimator = null;
            }
            mValueLabelAnimValue = 0;
            mValueLabelIsShowing = false;
            mShowValueLabelHandler.removeAllEvent();
        }

        if (true) {
            if (mPendingPosition != mMinProgress &&
                    !(mPendingPosition == mMaxProgress && mMode != MODE_NORMAL)) {
                animValueLabel();
            }
            if (mMaxProgress != -1 && mMode == MODE_RANGE) {
                if (Math.abs(mMinProgress - mPendingPosition) >
                        Math.abs(mMaxProgress - mPendingPosition)) {
                    mMaxProgress = mPendingPosition;
                } else {
                    mMinProgress = mPendingPosition;
                }
            } else {
                mMinProgress = mPendingPosition;
            }
        }

        checkOffsetBounds(true);
    }


    private void move(TouchEvent event, float length) {
        if (mPendingPosition == -1) {
            mOffset = 0;
            mMoveDetector.onTouchEvent(event);
            return;
        }
        if (mPendingPosition != mMinProgress && mPendingPosition != mMaxProgress) {
            float ps = mOrientation == HORIZONTAL ? event.getPointerScreenPosition(0)
                    .getX() : event.getPointerScreenPosition(0).getY();
            final int position = (int) getClosestPosition(ps, length)[0];
            if (position == mPendingPosition && !mSkipMove) {
                if (mMaxProgress == -1 && mMode == MODE_NORMAL) {
                    mPendingPosition = mMinProgress;
                } else {
                    if (Math.abs(mMinProgress - position) <=
                            Math.abs(mMaxProgress - position)) {
                        mPendingPosition = mMinProgress;
                    } else {
                        mPendingPosition = mMaxProgress;
                    }
                }

                if (mListener != null) {
                    if (mMaxProgress != -1 && mMode != MODE_NORMAL) {
                        if (mPendingPosition == mMinProgress) {
                            mListener.onValueChanged(position + mProgressOffset,
                                    mMaxProgress + mProgressOffset, true);
                        } else {
                            mListener.onValueChanged(mMinProgress + mProgressOffset,
                                    position + mProgressOffset, true);
                        }
                    } else {
                        mListener.onValueChanged(position + mProgressOffset, true);
                    }
                }

                setEnabled(false);

                mOffset = 0;
                AnimatorValue animator = new AnimatorValue();
                animator.setDuration(250);
                animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                    @Override
                    public void onUpdate(AnimatorValue animatorValue, float vs) {
                        mOffset = vs;
                        invalidate();
                    }
                });
                animator.setStateChangedListener(new Animator.StateChangedListener() {
                    @Override
                    public void onStart(Animator animator) {
                    }

                    @Override
                    public void onStop(Animator animator) {
                        mOffset = 0;
                        if (mPendingPosition == mMinProgress) {
                            mMinProgress = position;
                        } else if (mPendingPosition == mMaxProgress && mMode != MODE_NORMAL) {
                            mMaxProgress = position;
                        }
                        mPendingPosition = -1;
                        setEnabled(true);
                        invalidate();
                    }

                    @Override
                    public void onCancel(Animator animator) {
                    }

                    @Override
                    public void onEnd(Animator animator) {
                    }

                    @Override
                    public void onPause(Animator animator) {
                    }

                    @Override
                    public void onResume(Animator animator) {
                    }
                });

                animator.start();
            }
        } else {
            float ps = getPosition(length, mPendingPosition, true);
            float[] closestPosition = getClosestPosition(ps, length);
            float dis = closestPosition[1];
            int position = (int) closestPosition[0];

            if (mListener != null) {
                if (mMaxProgress != -1 && mMode != MODE_NORMAL) {
                    if (mPendingPosition == mMinProgress) {
                        mListener.onValueChanged(position + mProgressOffset,
                                mMaxProgress + mProgressOffset, true);
                    } else {
                        mListener.onValueChanged(mMinProgress + mProgressOffset,
                                position + mProgressOffset, true);
                    }
                } else {
                    mListener.onValueChanged(position + mProgressOffset, true);
                }
            }

            setEnabled(false);

            final int _position = position;
            AnimatorValue animator = new AnimatorValue();
            animator.setDuration(250);
            animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float vs) {
                    mOffset = vs;
                    invalidate();
                }
            });
            animator.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                    mOffset = 0;
                    if (mPendingPosition == mMinProgress) {
                        mMinProgress = _position;
                    } else if (mPendingPosition == mMaxProgress && mMode != MODE_NORMAL) {
                        mMaxProgress = _position;
                    }
                    if (mValueLabelAnimator == null) {
                        mPendingPosition = -1;
                        setEnabled(true);
                    } else {
                        mPendingPosition = _position;
                    }
                    invalidate();
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            });

            animator.start();

            hideValueLabel();
        }
        mPressedPosition = -1;
        invalidate();
    }


    private void cancell() {
        if (mPendingPosition == mMinProgress || mPendingPosition == mMaxProgress) {
            setEnabled(false);

            AnimatorValue animator = new AnimatorValue();
            animator.setDuration(250);
            animator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float vs) {
                    mOffset = vs;

                    mValueLabelAnimValue = (1 - vs);
                    generateValueLabelPath();
                    invalidate();
                }
            });
            animator.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                    mOffset = 0;
                    mPendingPosition = -1;
                    mPressedPosition = -1;
                    setEnabled(true);
                    invalidate();
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            });

            animator.start();
        } else {
            mOffset = 0;
            mPendingPosition = -1;
            mPressedPosition = -1;
            invalidate();
        }
        requestDisallowInterceptTouchEvent(getComponentParent(), false);
    }

    private void checkOffsetBounds(boolean isTouching) {
        float length = mLength - mTrackWidth;
        float ps = getPosition(length, mPendingPosition, false);
        if (mPendingPosition == mMinProgress) {
            mMinOffset = getPosition(length, 0, false) - ps;
            if (mMaxProgress != -1 && mMode == MODE_RANGE) {
                mMaxOffset = getPosition(length, mMaxProgress - 1, false) - ps;
            } else {
                mMaxOffset = getPosition(length, mCount - 1, false) - ps;
            }
            if (isTouching) {
                mPressedPosition = mPendingPosition;
            }
        } else if (mPendingPosition == mMaxProgress && mMode != MODE_NORMAL) {
            mMinOffset = getPosition(length, mMinProgress + 1, false) - ps;
            mMaxOffset = getPosition(length, mCount - 1, false) - ps;
            if (isTouching) {
                mPressedPosition = mPendingPosition;
            }
        } else if (!isClickable()) {
            mPressedPosition = mPendingPosition = -1;
        }
    }

    private void animValueLabel() {
        mValueLabelIsShowing = true;
        mShowValueLabelHandler.removeAllEvent();

        float value = mValueLabelAnimValue;
        if (mValueLabelAnimator != null) {
            mValueLabelAnimator.cancel();
        }
        if (value == 1) {
            mValueLabelAnimator = null;
            generateValueLabelPath();
            return;
        }

        mValueLabelAnimator = new AnimatorValue();
        mValueLabelAnimator.setDuration(Math.round((250 * (1 - value))));
        mValueLabelAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
            @Override
            public void onUpdate(AnimatorValue animatorValue, float vs) {
                mValueLabelAnimValue = vs;
                generateValueLabelPath();
                invalidate();
            }
        });
        mValueLabelAnimator.start();
    }

    private void hideValueLabel() {
        mValueLabelIsShowing = false;
        mShowValueLabelHandler.removeAllEvent();

        float value = mValueLabelAnimValue;
        if (mValueLabelAnimator != null) {
            mValueLabelAnimator.cancel();
        }

        if (value > 0) {
            mValueLabelAnimator = new AnimatorValue();
            mValueLabelAnimator.setDuration(Math.round(250 * value));
            mValueLabelAnimator.setValueUpdateListener(new AnimatorValue.ValueUpdateListener() {
                @Override
                public void onUpdate(AnimatorValue animatorValue, float vs) {
                    mValueLabelAnimValue = vs;
                    generateValueLabelPath();
                    invalidate();
                }
            });

            mValueLabelAnimator.setStateChangedListener(new Animator.StateChangedListener() {
                @Override
                public void onStart(Animator animator) {
                }

                @Override
                public void onStop(Animator animator) {
                    mValueLabelAnimator = null;
                    if (mOffset == 0) {
                        mPendingPosition = -1;
                        setEnabled(true);
                    }
                    invalidate();
                }

                @Override
                public void onCancel(Animator animator) {
                }

                @Override
                public void onEnd(Animator animator) {
                }

                @Override
                public void onPause(Animator animator) {
                }

                @Override
                public void onResume(Animator animator) {
                }
            });
            mValueLabelAnimator.start();
        } else {
            mValueLabelAnimator = null;
        }
    }

    private void showMinValueLabel() {
        mPendingPosition = mMinProgress;
        showValueLabel();
    }

    private void showMaxValueLabel() {
        mPendingPosition = mMaxProgress;
        showValueLabel();
    }

    private void showValueLabel() {
        animValueLabel();
        Runnable task1 = new Runnable() {
            @Override
            public void run() {
                hideValueLabel();
            }
        };

        mShowValueLabelHandler.postTask(task1, mValueLabelDuration - 250, EventHandler.Priority.IMMEDIATE);
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        generateInactiveTrackPath();
        boolean isValueLabelVisible =
                (mValueLabelMode & 0x1) == 1 || (mValueLabelMode >> 1 & 0x1) == 1;

        float length = mLength - mTrackWidth;
        mPaint.setColor(new Color(mInactiveTrackColor));
        canvas.drawPath(mInactiveTrackPath, mPaint);

        float min;
        float max;
        mPaint.setColor(new Color(mTrackColor));
        if (mOrientation == HORIZONTAL) {
            float top = ((getHeight() - getPaddingTop() - getPaddingBottom()) - mTrackWidth) / 2f +
                    getPaddingTop();
            float bottom = top + mTrackWidth;
            if (mMode != MODE_NORMAL && mMaxProgress != -1) {
                float left = min = getPosition(length, mMinProgress, true) - mTrackWidth / 2f;
                float right = max = getPosition(length, mMaxProgress, true) + mTrackWidth / 2f;
                mRectF.modify(left, top, right, bottom);
                canvas.drawRoundRect(mRectF, mTrackWidth / 2f, mTrackWidth / 2f, mPaint);
            } else {
                float left = min = getPosition(length, 0, false) - mTrackWidth / 2f;
                float right = max = getPosition(length, mMinProgress, true) + mTrackWidth / 2f;
                mRectF.modify(left, top, right, bottom);
                if (mTickMarkPatterns == null || mTickMarkPatterns.size() == 0 ||
                        mTickMarkPatterns.get(0) instanceof Dot) {
                    canvas.drawRoundRect(mRectF, mTrackWidth / 2f, mTrackWidth / 2f, mPaint);
                } else {
                    canvas.drawRect(mRectF, mPaint);
                }
            }
        } else {
            float left = ((getWidth() - getPaddingLeft() - getPaddingRight()) - mTrackWidth) / 2f +
                    getPaddingLeft();
            float right = left + mTrackWidth;
            if (mMode != MODE_NORMAL && mMaxProgress != -1) {
                float top = min = getPosition(length, mMinProgress, true) - mTrackWidth / 2f;
                float bottom = max = getPosition(length, mMaxProgress, true) + mTrackWidth / 2f;
                mRectF.modify(left, top, right, bottom);
                canvas.drawRoundRect(mRectF, mTrackWidth / 2f, mTrackWidth / 2f, mPaint);
            } else {
                float top = min = getPosition(length, 0, false) - mTrackWidth / 2f;
                float bottom = max = getPosition(length, mMinProgress, true) + mTrackWidth / 2f;
                mRectF.modify(left, top, right, bottom);
                if (mTickMarkPatterns == null || mTickMarkPatterns.size() == 0 ||
                        mTickMarkPatterns.get(0) instanceof Dot) {
                    canvas.drawRoundRect(mRectF, mTrackWidth / 2f, mTrackWidth / 2f, mPaint);
                } else {
                    canvas.drawRect(mRectF, mPaint);
                }
            }
        }

        float cx = (getWidth() - getPaddingLeft() - getPaddingRight()) / 2f + getPaddingLeft();
        float cy = (getHeight() - getPaddingTop() - getPaddingBottom()) / 2f + getPaddingTop();

        if (mTickMarkPatterns != null && mTickMarkPatterns.size() > 0) {
            if (mOrientation == HORIZONTAL) {
                for (int i = 0; i < mCount; i++) {
                    if (i % mTickMarkStep != 0) {
                        continue;
                    }
                    Object pattern = mTickMarkPatterns.get(i % mTickMarkPatterns.size());
                    cx = getPosition(length, i, false);

                    if (min <= cx && cx <= max) {
                        mPaint.setColor(new Color(mTickMarkColor));
                    } else {
                        mPaint.setColor(new Color(mTickMarkInactiveColor));
                    }
                    if (pattern instanceof Dot) {
                        canvas.drawCircle(cx, cy, mTrackWidth / 2f, mPaint);
                    } else {
                        float dashLength = ((Dash) pattern).length;
                        canvas.drawRect(new RectFloat(cx - dashLength / 2f, cy - mTrackWidth / 2f,
                                cx + dashLength / 2f, cy + mTrackWidth / 2f), mPaint);
                    }
                }
            } else {
                for (int i = 0; i < mCount; i++) {
                    if (i % mTickMarkStep != 0) {
                        continue;
                    }

                    Object pattern = mTickMarkPatterns.get(i % mTickMarkPatterns.size());
                    cy = getPosition(length, i, false);

                    if (min <= cy && cy <= max) {
                        mPaint.setColor(new Color(mTickMarkColor));
                    } else {
                        mPaint.setColor(new Color(mTickMarkInactiveColor));
                    }

                    if (pattern instanceof Dot) {
                        canvas.drawCircle(cx, cy, mTrackWidth / 2f, mPaint);
                    } else {
                        float dashLength = ((Dash) pattern).length;
                        canvas.drawRect(new RectFloat(cx - mTrackWidth / 2f, cy - dashLength / 2f,
                                cx + mTrackWidth / 2f, cy + dashLength / 2f), mPaint);
                    }
                }
            }
        }

        if (mOrientation == HORIZONTAL) {
            cx = getPosition(length, mMinProgress, true);
        } else {
            cy = getPosition(length, mMinProgress, true);
        }

        float _cx = cx;
        float _cy = cy;
        float dp6 = Utils.convertDpToPixel(6, getContext());
        float ratio = mRadius / dp6;
        if (mOrientation == HORIZONTAL) {
            if (mValueLabelGravity == TOP) {
                _cy -= dp6 + Utils.convertDpToPixel(16, getContext()) + dp6 * 3;
                _cy = cy + (_cy - cy) * mValueLabelAnimValue * ratio;
            } else if (mValueLabelGravity == BOTTOM) {
                _cy += dp6 + Utils.convertDpToPixel(16, getContext()) + dp6 * 3;
                _cy = cy + (_cy - cy) * mValueLabelAnimValue * ratio;
            }
        } else {
            if (mValueLabelGravity == RIGHT) {
                _cx += dp6 + Utils.convertDpToPixel(16, getContext()) + dp6 * 3;
                _cx = cx + (_cx - cx) * mValueLabelAnimValue * ratio;
            } else if (mValueLabelGravity == LEFT) {
                _cx -= dp6 + Utils.convertDpToPixel(16, getContext()) + dp6 * 3;
                _cx = cx + (_cx - cx) * mValueLabelAnimValue * ratio;
            }
        }

        if (mPendingPosition == mMinProgress && mPendingPosition != -1 &&
                mValueLabelAnimValue > 0 && isValueLabelVisible) {
            mPaint.setColor(new Color(mThumbColor));
            canvas.drawPath(mValueLabelPath, mPaint);
            canvas.drawCircle(_cx, _cy, mRadius * 3 * mValueLabelAnimValue, mPaint);
            drawValueLabel(canvas, cx, cy, _cx, _cy, length);
        }

        // 拇指
        onDrawThumb(canvas, cx, cy, mPressedPosition != -1 && mPressedPosition == mMinProgress);

        int progress;
        if (mOrientation == HORIZONTAL) {
            progress = (int) getClosestPosition(cx, length)[0];
        } else {
            progress = (int) getClosestPosition(cy, length)[0];
        }
        if (mTmpMinProgress != progress) {
            mTmpMinProgress = progress;
            if (mListener != null && mValueChangedImmediately) {
                if (mMaxProgress != -1 && mMode != MODE_NORMAL) {
                    mListener.onValueChanged(progress + mProgressOffset,
                            mMaxProgress + mProgressOffset, true);
                } else {
                    mListener.onValueChanged(progress + mProgressOffset, true);
                }
            }
        }

        if (mMaxProgress != -1 && mMode != MODE_NORMAL) {
            mPaint.setColor(new Color(mThumbColor));
            if (mOrientation == HORIZONTAL) {
                cx = getPosition(length, mMaxProgress, true);
                _cx = cx;
                progress = (int) getClosestPosition(cx, length)[0];
            } else {
                cy = getPosition(length, mMaxProgress, true);
                _cy = cy;
                progress = (int) getClosestPosition(cy, length)[0];
            }
            if (mPendingPosition == mMaxProgress && mValueLabelAnimValue > 0 &&
                    isValueLabelVisible) {
                canvas.drawPath(mValueLabelPath, mPaint);
                canvas.drawCircle(_cx, _cy, mRadius * 3 * mValueLabelAnimValue, mPaint);
                drawValueLabel(canvas, cx, cy, _cx, _cy, length);
            }

            onDrawThumb(canvas, cx, cy, mPressedPosition != -1 && mPressedPosition == mMaxProgress);

            if (mTmpMaxProgress != progress) {
                mTmpMaxProgress = progress;

                if (mListener != null && mValueChangedImmediately) {
                    if (mMaxProgress != -1 && mMode != MODE_NORMAL) {
                        mListener.onValueChanged(mMinProgress + mProgressOffset,
                                progress + mProgressOffset, true);
                    }
                }
            }
        }
    }


    /**
     * 画拇指
     *
     * @param canvas     画布
     * @param cx         X值
     * @param cy         Y值
     * @param hasTouched 是否 hasTouched
     */
    public void onDrawThumb(Canvas canvas, float cx, float cy, boolean hasTouched) {
        if (hasTouched) {
            mPaint.setColor(new Color(mThumbPressedColor));
            canvas.drawCircle(cx, cy, mRadius * 3.5f, mPaint);
        }
        mPaint.setColor(new Color(mThumbColor));
        canvas.drawCircle(cx, cy, mRadius, mPaint);
    }

    private void drawValueLabel(Canvas canvas, float cx, float cy, float _cx, float _cy,
                                float length) {
        if (mValueLabelGravity == TOP && _cy + mRadius * 3 * mValueLabelAnimValue > cy - mRadius) {
            return;
        } else if (mValueLabelGravity == BOTTOM &&
                _cy - mRadius * 3 * mValueLabelAnimValue < cy + mRadius) {
            return;
        } else if (mValueLabelGravity == RIGHT &&
                _cx - mRadius * 3 * mValueLabelAnimValue < cx + mRadius) {
            return;
        } else if (mValueLabelGravity == LEFT &&
                _cx + mRadius * 3 * mValueLabelAnimValue > cx - mRadius) {
            return;
        }

        String label;
        if (mOrientation == HORIZONTAL) {
            label = mValueLabelFormatter
                    .getLabel((int) getClosestPosition(cx, length)[0] + mProgressOffset);
        } else {
            label = mValueLabelFormatter
                    .getLabel((int) getClosestPosition(cy, length)[0] + mProgressOffset);
        }
        if (!TextUtils.isEmpty(label)) {
            mPaint.setTextSize((int) (mValueLabelTextSize * mValueLabelAnimValue));
            mPaint.setColor(new Color(mValueLabelTextColor));
            mPaint.setTextAlign(TextAlignment.CENTER);
            mPaint.getTextBounds(label);
            canvas.drawText(mPaint, label, _cx - mBounds.getWidth() / 2f - mBounds.left,
                    _cy + mBounds.getHeight() / 2f - mBounds.bottom + 12);
        }
    }

    private class MoveListener extends MoveGestureDetector.SimpleOnMoveGestureListener {
        @Override
        public boolean onMove(MoveGestureDetector detector) {
            Point ds = detector.getFocusDelta();
            if (mOrientation == HORIZONTAL) {
                mOffset += ds.getPointX();
            } else {
                mOffset += ds.getPointY();
            }
            if ((mPendingPosition == mMinProgress ||
                    mPendingPosition == mMaxProgress && mMode != MODE_NORMAL) &&
                    mPendingPosition != -1) {
                mOffset = Math.min(Math.max(mOffset, mMinOffset), mMaxOffset);
                generateValueLabelPath();
                if (Math.abs(mOffset) >= mRadius * 2 && !mValueLabelIsShowing &&
                        (mValueLabelMode & 0x1) == 1) {
                    animValueLabel();
                } else if ((mValueLabelMode & 0x1) == 1) {
                    mShowValueLabelHandler.removeAllEvent();
                }
            } else if (Math.abs(mOffset) >= mRadius * 3.5) {
                mSkipMove = true;
            }
            return true;
        }
    }

    private void generateValueLabelPath() {
        float r2 = Utils.convertDpToPixel(6, getContext());
        float cx2;
        float cy2;
        float r1 = r2 * 3;
        float cx1;
        float cy1;
        float ratio = mRadius / r2;

        float length = mLength - mTrackWidth;
        if (mPendingPosition == mMinProgress || mMaxProgress != -1 && mMode != MODE_NORMAL) {
            if (mOrientation == HORIZONTAL) {
                cy2 = (getHeight() - getPaddingTop() - getPaddingBottom()) / 2f + getPaddingTop();
                cx2 = getPosition(length, mPendingPosition, true);
            } else {
                cy2 = getPosition(length, mPendingPosition, true);
                cx2 = (getWidth() - getPaddingLeft() - getPaddingRight()) / 2f + getPaddingLeft();
            }
        } else {
            mValueLabelPath.reset();
            return;
        }
        cx1 = cx2;
        cy1 = cy2;

        float dp1 = Utils.convertDpToPixel(1, getContext());
        float dp16 = Utils.convertDpToPixel(16, getContext());
        float ox1;
        float oy1;
        float ox2;
        float oy2;
        if (mValueLabelGravity == TOP) {
            cy1 -= r2 + dp16 + r1;
            ox1 = -Math.max((r2 - dp1), 0);
            ox2 = -ox1;
            oy1 = oy2 = r1 + dp16 / 2;
        } else if (mValueLabelGravity == BOTTOM) {
            cy1 += r2 + dp16 + r1;
            ox1 = Math.max((r2 - dp1), 0);
            ox2 = -ox1;
            oy1 = oy2 = -r1 - dp16 / 2;
        } else if (mValueLabelGravity == RIGHT) {
            cx1 += r2 + dp16 + r1;
            ox1 = ox2 = -r1 - dp16 / 2;
            oy1 = -Math.max((r2 - dp1), 0);
            oy2 = -oy1;
        } else {
            cx1 -= r2 + dp16 + r1;
            ox1 = ox2 = r1 + dp16 / 2;
            oy1 = Math.max((r2 - dp1), 0);
            oy2 = -oy1;
        }

        if (mValueLabelGravity == TOP && cy1 + r1 >= cy2 - r2) {
            mValueLabelPath.reset();
            return;
        } else if (mValueLabelGravity == BOTTOM && cy1 - r1 <= cy2 + r2) {
            mValueLabelPath.reset();
            return;
        } else if (mValueLabelGravity == RIGHT && cx1 - r1 <= cx2 + r2) {
            mValueLabelPath.reset();
            return;
        } else if (mValueLabelGravity == LEFT && cx1 + r1 >= cx2 - r2) {
            mValueLabelPath.reset();
            return;
        }

        mValueLabelPath.reset();
        mRectF.modify(cx1 - r1, cy1 - r1, cx1 + r1, cy1 + r1);
        mValueLabelPath.arcTo(mRectF, 135 + mValueLabelGravity, 270, true);
        mValueLabelPath.quadTo(cx1 + ox1, cy1 + oy1,
                cx2 + r2 * (float) Math.cos(Math.toRadians(-45 + mValueLabelGravity)),
                cy2 + r2 * (float) Math.sin(Math.toRadians(-45 + mValueLabelGravity)));
        mRectF.modify(cx2 - r2, cy2 - r2, cx2 + r2, cy2 + r2);
        mValueLabelPath.arcTo(mRectF, -45 + mValueLabelGravity, 270, true);
        mValueLabelPath.quadTo(cx1 + ox2, cy1 + oy2,
                cx1 + r1 * (float) Math.cos(Math.toRadians(135 + mValueLabelGravity)),
                cy1 + r1 * (float) Math.sin(Math.toRadians(135 + mValueLabelGravity)));
        mValueLabelPath
                .moveTo(cx1 + r1 * (float) Math.cos(Math.toRadians(135 + mValueLabelGravity)),
                        cy1 + r1 * (float) Math.sin(Math.toRadians(135 + mValueLabelGravity)));
        mValueLabelPath.close();

        if (mValueLabelAnimValue * ratio != 1) {
            mValueLabelPath.computeBounds(mRectF);
            if (mOrientation == HORIZONTAL) {
                mValueLabelMatrix
                        .setScale(mValueLabelAnimValue * ratio, mValueLabelAnimValue * ratio,
                                mRectF.getCenter().getPointX(), cy2);
            } else {
                mValueLabelMatrix
                        .setScale(mValueLabelAnimValue * ratio, mValueLabelAnimValue * ratio, cx2,
                                mRectF.getCenter().getPointY());
            }
            mValueLabelPath.transform(mValueLabelMatrix);
        }
    }

    private float[] getClosestPosition(float ps, float length) {
        float dis = Float.MAX_VALUE;
        int position = -1;
        for (int i = 0; i < mCount; i++) {
            float _dis = getPosition(length, i, false) - ps;
            if (Math.abs(_dis) < Math.abs(dis)) {
                dis = _dis;
                position = i;
            }
        }
        return new float[]{position, dis};
    }

    private float getPosition(float length, int progress, boolean withOffset) {
        if (mOrientation == HORIZONTAL) {
            return getPaddingLeft() + length / (mCount - 1) * progress + mRadius +
                    (withOffset && mPendingPosition == progress ? mOffset : 0);
        } else {
            return getPaddingTop() + length / (mCount - 1) * progress + mRadius +
                    (withOffset && mPendingPosition == progress ? mOffset : 0);
        }
    }

    /**
     * ValueLabelFormatter
     */
    public static abstract class ValueLabelFormatter {
        /**
         * 获取Label
         *
         * @param input input
         * @return String
         */
        public abstract String getLabel(int input);
    }

    /**
     * OnValueChangedListener
     */
    public static class OnValueChangedListener {
        // Only called when mode is {@Code MODE_NORMAL}

        /**
         * 值变化
         *
         * @param progress 值
         * @param fromUser 是否User
         */
        public void onValueChanged(int progress, boolean fromUser) {
        }

        // Only called when mode is {@Code MODE_RANGE}

        /**
         * 值变化
         *
         * @param minProgress 最小值
         * @param maxProgress 最大值
         * @param fromUser    是否User
         */
        public void onValueChanged(int minProgress, int maxProgress, boolean fromUser) {
        }
    }
}
